{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import io\n",
    "import os\n",
    "import platform\n",
    "import pdb\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from matplotlib.pyplot import cm\n",
    "\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from sklearn import preprocessing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "data=pd.read_csv('toronto.csv')\n",
    "data.fillna(np.mean(data),inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Temp (°C)</th>\n",
       "      <th>Dew Point Temp (°C)</th>\n",
       "      <th>Rel Hum (%)</th>\n",
       "      <th>Wind Dir (10s deg)</th>\n",
       "      <th>Wind Spd (km/h)</th>\n",
       "      <th>Visibility (km)</th>\n",
       "      <th>Stn Press (kPa)</th>\n",
       "      <th>Wind Chill</th>\n",
       "      <th>NO</th>\n",
       "      <th>NO2</th>\n",
       "      <th>NOx</th>\n",
       "      <th>O3</th>\n",
       "      <th>PM25</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>-6.2</td>\n",
       "      <td>-13.2</td>\n",
       "      <td>58.0</td>\n",
       "      <td>25.0</td>\n",
       "      <td>43.0</td>\n",
       "      <td>16.1</td>\n",
       "      <td>100.68</td>\n",
       "      <td>-16.0</td>\n",
       "      <td>3</td>\n",
       "      <td>15</td>\n",
       "      <td>18</td>\n",
       "      <td>19</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>-6.0</td>\n",
       "      <td>-13.7</td>\n",
       "      <td>55.0</td>\n",
       "      <td>24.0</td>\n",
       "      <td>41.0</td>\n",
       "      <td>16.1</td>\n",
       "      <td>100.59</td>\n",
       "      <td>-16.0</td>\n",
       "      <td>2</td>\n",
       "      <td>14</td>\n",
       "      <td>16</td>\n",
       "      <td>19</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>-6.2</td>\n",
       "      <td>-13.3</td>\n",
       "      <td>57.0</td>\n",
       "      <td>24.0</td>\n",
       "      <td>45.0</td>\n",
       "      <td>16.1</td>\n",
       "      <td>100.56</td>\n",
       "      <td>-16.0</td>\n",
       "      <td>1</td>\n",
       "      <td>10</td>\n",
       "      <td>11</td>\n",
       "      <td>22</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>-6.0</td>\n",
       "      <td>-12.0</td>\n",
       "      <td>63.0</td>\n",
       "      <td>24.0</td>\n",
       "      <td>32.0</td>\n",
       "      <td>16.1</td>\n",
       "      <td>100.58</td>\n",
       "      <td>-15.0</td>\n",
       "      <td>1</td>\n",
       "      <td>10</td>\n",
       "      <td>11</td>\n",
       "      <td>21</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>-6.0</td>\n",
       "      <td>-12.2</td>\n",
       "      <td>62.0</td>\n",
       "      <td>24.0</td>\n",
       "      <td>35.0</td>\n",
       "      <td>16.1</td>\n",
       "      <td>100.50</td>\n",
       "      <td>-15.0</td>\n",
       "      <td>2</td>\n",
       "      <td>11</td>\n",
       "      <td>13</td>\n",
       "      <td>19</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   Temp (°C)  Dew Point Temp (°C)  Rel Hum (%)  Wind Dir (10s deg)  \\\n",
       "0       -6.2                -13.2         58.0                25.0   \n",
       "1       -6.0                -13.7         55.0                24.0   \n",
       "2       -6.2                -13.3         57.0                24.0   \n",
       "3       -6.0                -12.0         63.0                24.0   \n",
       "4       -6.0                -12.2         62.0                24.0   \n",
       "\n",
       "   Wind Spd (km/h)  Visibility (km)  Stn Press (kPa)  Wind Chill  NO  NO2  \\\n",
       "0             43.0             16.1           100.68       -16.0   3   15   \n",
       "1             41.0             16.1           100.59       -16.0   2   14   \n",
       "2             45.0             16.1           100.56       -16.0   1   10   \n",
       "3             32.0             16.1           100.58       -15.0   1   10   \n",
       "4             35.0             16.1           100.50       -15.0   2   11   \n",
       "\n",
       "   NOx  O3  PM25  \n",
       "0   18  19     6  \n",
       "1   16  19     7  \n",
       "2   11  22     7  \n",
       "3   11  21     7  \n",
       "4   13  19     7  "
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "dataset=pd.DataFrame(data,columns=data.columns[:])\n",
    "scaler = preprocessing.MinMaxScaler() \n",
    "scaled_values = scaler.fit_transform(dataset) \n",
    "dataset.loc[:,:] = scaled_values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Temp (°C)</th>\n",
       "      <th>Dew Point Temp (°C)</th>\n",
       "      <th>Rel Hum (%)</th>\n",
       "      <th>Wind Dir (10s deg)</th>\n",
       "      <th>Wind Spd (km/h)</th>\n",
       "      <th>Visibility (km)</th>\n",
       "      <th>Stn Press (kPa)</th>\n",
       "      <th>Wind Chill</th>\n",
       "      <th>NO</th>\n",
       "      <th>NO2</th>\n",
       "      <th>NOx</th>\n",
       "      <th>O3</th>\n",
       "      <th>PM25</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.340037</td>\n",
       "      <td>0.334572</td>\n",
       "      <td>0.468354</td>\n",
       "      <td>0.685714</td>\n",
       "      <td>0.605634</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.457529</td>\n",
       "      <td>0.631579</td>\n",
       "      <td>3</td>\n",
       "      <td>15</td>\n",
       "      <td>18</td>\n",
       "      <td>19</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0.343693</td>\n",
       "      <td>0.325279</td>\n",
       "      <td>0.430380</td>\n",
       "      <td>0.657143</td>\n",
       "      <td>0.577465</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.440154</td>\n",
       "      <td>0.631579</td>\n",
       "      <td>2</td>\n",
       "      <td>14</td>\n",
       "      <td>16</td>\n",
       "      <td>19</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0.340037</td>\n",
       "      <td>0.332714</td>\n",
       "      <td>0.455696</td>\n",
       "      <td>0.657143</td>\n",
       "      <td>0.633803</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.434363</td>\n",
       "      <td>0.631579</td>\n",
       "      <td>1</td>\n",
       "      <td>10</td>\n",
       "      <td>11</td>\n",
       "      <td>22</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0.343693</td>\n",
       "      <td>0.356877</td>\n",
       "      <td>0.531646</td>\n",
       "      <td>0.657143</td>\n",
       "      <td>0.450704</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.438224</td>\n",
       "      <td>0.657895</td>\n",
       "      <td>1</td>\n",
       "      <td>10</td>\n",
       "      <td>11</td>\n",
       "      <td>21</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0.343693</td>\n",
       "      <td>0.353160</td>\n",
       "      <td>0.518987</td>\n",
       "      <td>0.657143</td>\n",
       "      <td>0.492958</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.422780</td>\n",
       "      <td>0.657895</td>\n",
       "      <td>2</td>\n",
       "      <td>11</td>\n",
       "      <td>13</td>\n",
       "      <td>19</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   Temp (°C)  Dew Point Temp (°C)  Rel Hum (%)  Wind Dir (10s deg)  \\\n",
       "0   0.340037             0.334572     0.468354            0.685714   \n",
       "1   0.343693             0.325279     0.430380            0.657143   \n",
       "2   0.340037             0.332714     0.455696            0.657143   \n",
       "3   0.343693             0.356877     0.531646            0.657143   \n",
       "4   0.343693             0.353160     0.518987            0.657143   \n",
       "\n",
       "   Wind Spd (km/h)  Visibility (km)  Stn Press (kPa)  Wind Chill  NO  NO2  \\\n",
       "0         0.605634              1.0         0.457529    0.631579   3   15   \n",
       "1         0.577465              1.0         0.440154    0.631579   2   14   \n",
       "2         0.633803              1.0         0.434363    0.631579   1   10   \n",
       "3         0.450704              1.0         0.438224    0.657895   1   10   \n",
       "4         0.492958              1.0         0.422780    0.657895   2   11   \n",
       "\n",
       "   NOx  O3  PM25  \n",
       "0   18  19     6  \n",
       "1   16  19     7  \n",
       "2   11  22     7  \n",
       "3   11  21     7  \n",
       "4   13  19     7  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_set=np.asarray(dataset,dtype=np.float32)\n",
    "seq_len=30 + 1\n",
    "x=len(data_set)-seq_len\n",
    "sequences = [data_set[t:t+seq_len] for t in range(x)]\n",
    "seq=torch.FloatTensor(sequences)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([8729, 31, 13])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "seq.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "split_row=round(0.90*seq.size(0))\n",
    "x_train_set=seq[:split_row, :-1]\n",
    "y_train_set=seq[:split_row, -1]\n",
    "x_valid_set=seq[split_row:, :-1]\n",
    "y_valid_set=seq[split_row:, -1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class LSTM(nn.Module):\n",
    "    def __init__(self,input_size,hidden_size,num_layers=1,dropout=0,bidirectional=False):\n",
    "        super(LSTM,self).__init__()\n",
    "        self.input_size=input_size\n",
    "        self.hidden_size=hidden_size\n",
    "        self.num_layers=num_layers\n",
    "        self.dropout=dropout\n",
    "        self.bidirectional=bidirectional\n",
    "        self.lstm = nn.LSTM(input_size,\n",
    "                            hidden_size,\n",
    "                            num_layers,\n",
    "                            dropout=dropout,\n",
    "                            bidirectional=bidirectional)\n",
    "        self.linear = nn.Linear(hidden_size, 13)\n",
    "        \n",
    "    def forward(self,inputs,hidden):\n",
    "        outputs,hidden=self.lstm(inputs,hidden)\n",
    "        predictions=self.linear(outputs[-1])\n",
    "        return predictions,outputs,hidden\n",
    "    \n",
    "    def init_hidden(self,batch_size):\n",
    "        num_directions=2 if self.bidirectional else 1\n",
    "        hidden = (torch.zeros(self.num_layers*num_directions, batch_size, self.hidden_size),\n",
    "                  torch.zeros(self.num_layers*num_directions, batch_size, self.hidden_size))\n",
    "        return hidden"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_batch(x,y,i,batch_size):\n",
    "    if x.dim() == 2:\n",
    "        x = x.unsqueeze(2)\n",
    "    batch_x = x[(i*batch_size):(i*batch_size)+batch_size, :, :]\n",
    "    batch_y = y[(i*batch_size):(i*batch_size)+batch_size]\n",
    "\n",
    "    # Reshape Tensors into (seq_len, batch_size, input_size) format for the LSTM.\n",
    "    batch_x = batch_x.transpose(0, 1)\n",
    "    \n",
    "    return batch_x, batch_y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(model,x_train_set,y_train_set,optimizer,batch_size,epoch):\n",
    "    num_sequences=x_train_set.size(0)\n",
    "    num_batches=num_sequences//batch_size\n",
    "    \n",
    "    total_loss=0\n",
    "    \n",
    "    model.train()\n",
    "    for i in range(num_batches):\n",
    "        # Get input and target batches and reshape for LSTM.\n",
    "        batch_x, batch_y = get_batch(x_train_set, y_train_set, i, batch_size)\n",
    "\n",
    "        # Reset the gradient.\n",
    "        lstm.zero_grad()\n",
    "        \n",
    "        # Initialize the hidden states (see the function lstm.init_hidden(batch_size)).\n",
    "        hidden = lstm.init_hidden(batch_size)\n",
    "        \n",
    "        # Complete a forward pass.\n",
    "        y_pred, outputs, hidden = lstm(batch_x,hidden)\n",
    "        \n",
    "        # Calculate the loss with the 'loss_fn'.\n",
    "        loss = loss_fn(y_pred,batch_y)\n",
    "        \n",
    "        # Compute the gradient.\n",
    "        loss.backward()\n",
    "        \n",
    "        # Clip to the gradient to avoid exploding gradient.\n",
    "        nn.utils.clip_grad_norm_(lstm.parameters(), max_grad_norm)\n",
    "\n",
    "        # Make one step with optimizer.\n",
    "        optimizer.step()\n",
    "        \n",
    "        # Accumulate the total loss.\n",
    "        total_loss += loss.data\n",
    "        \n",
    "    print(\"Epoch {}: Loss = {:.8f}\".format(epoch+1, total_loss/num_batches))\n",
    "    return total_loss/num_batches"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def eval(model,x_valid_set,y_valid_set,optimizer,batch_size):\n",
    "    num_sequences=x_valid_set.size(0)\n",
    "    num_batches=num_sequences//batch_size\n",
    "    \n",
    "    total_loss=0\n",
    "    \n",
    "    model.eval()\n",
    "    for i in range(num_batches):\n",
    "        # Get input and target batches and reshape for LSTM.\n",
    "        batch_x, batch_y = get_batch(x_valid_set, y_valid_set, i, batch_size)\n",
    "\n",
    "        # Reset the gradient.\n",
    "        lstm.zero_grad()\n",
    "        \n",
    "        # Initialize the hidden states (see the function lstm.init_hidden(batch_size)).\n",
    "        hidden = lstm.init_hidden(batch_size)\n",
    "        \n",
    "        # Complete a forward pass.\n",
    "        y_pred, outputs, hidden = lstm(batch_x,hidden)\n",
    "        \n",
    "        # Calculate the loss with the 'loss_fn'.\n",
    "        loss = loss_fn(y_pred,batch_y)\n",
    "        \n",
    "        # Compute the gradient.\n",
    "        loss.backward()\n",
    "        \n",
    "        # Clip to the gradient to avoid exploding gradient.\n",
    "        nn.utils.clip_grad_norm_(lstm.parameters(), max_grad_norm)\n",
    "\n",
    "        # Make one step with optimizer.\n",
    "        optimizer.step()\n",
    "        \n",
    "        # Accumulate the total loss.\n",
    "        total_loss += loss.data\n",
    "\n",
    "    print(\"Validation: Loss = {:.8f}\".format(total_loss/num_batches))\n",
    "    return total_loss/num_batches"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def save_model(epoch, model, path='./'):\n",
    "    \n",
    "    # file name and path \n",
    "    filename = path + 'neural_network_{}.pt'.format(epoch)\n",
    "    \n",
    "    # load the model parameters \n",
    "    torch.save(model.state_dict(), filename)\n",
    "    \n",
    "    \n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_model(epoch, model, path='./'):\n",
    "    \n",
    "    # file name and path \n",
    "    filename = path + 'neural_network_{}.pt'.format(epoch)\n",
    "    \n",
    "    # load the model parameters \n",
    "    model.load_state_dict(torch.load(filename))\n",
    "    \n",
    "    \n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training model for 30 epoch\n",
      "Epoch 1: Loss = 0.02051846\n",
      "Validation: Loss = 0.02046269\n",
      "Epoch 2: Loss = 0.01477920\n",
      "Validation: Loss = 0.01664688\n",
      "Epoch 3: Loss = 0.01379875\n",
      "Validation: Loss = 0.01601278\n",
      "Epoch 4: Loss = 0.01344827\n",
      "Validation: Loss = 0.01590238\n",
      "Epoch 5: Loss = 0.01327482\n",
      "Validation: Loss = 0.01612095\n",
      "Epoch 6: Loss = 0.01307748\n",
      "Validation: Loss = 0.01528993\n",
      "Epoch 7: Loss = 0.01316451\n",
      "Validation: Loss = 0.01589337\n",
      "Epoch 8: Loss = 0.01313053\n",
      "Validation: Loss = 0.01581030\n",
      "Epoch 9: Loss = 0.01308308\n",
      "Validation: Loss = 0.01545099\n",
      "Epoch 10: Loss = 0.01294294\n",
      "Validation: Loss = 0.01530129\n",
      "Epoch 11: Loss = 0.01302366\n",
      "Validation: Loss = 0.01536266\n",
      "Epoch 12: Loss = 0.01304221\n",
      "Validation: Loss = 0.01582655\n",
      "Epoch 13: Loss = 0.01297798\n",
      "Validation: Loss = 0.01538026\n"
     ]
    }
   ],
   "source": [
    "input_size=13\n",
    "hidden_size=24\n",
    "num_layers=2\n",
    "lstm=LSTM(input_size,hidden_size,num_layers)\n",
    "\n",
    "learning_rate=0.01\n",
    "max_grad_norm=5\n",
    "loss_fn = nn.MSELoss()\n",
    "optimizer = optim.Adam(lstm.parameters(), lr=learning_rate,weight_decay=0.0001)\n",
    "\n",
    "batch_size = 8\n",
    "num_epochs = 30 #3\n",
    "# num_sequences = x_train_set.size(0)\n",
    "# num_batches = num_sequences //batch_size\n",
    "\n",
    "checkpoint_freq = 10\n",
    "path = './'\n",
    "\n",
    "train_losses=[]\n",
    "valid_losses=[]\n",
    "\n",
    "print(\"Training model for {} epoch\".format(num_epochs))\n",
    "for epoch in range(num_epochs):\n",
    "#     total_loss = 0\n",
    "\n",
    "    # Shuffle input and target sequences.\n",
    "    idx = torch.randperm(x_train_set.size(0))\n",
    "    x = x_train_set[idx]\n",
    "    y = y_train_set[idx]\n",
    "    \n",
    "    train_loss=train(lstm,x_train_set,y_train_set,optimizer,batch_size,epoch)\n",
    "    valid_loss=eval(lstm,x_valid_set,y_valid_set,optimizer,batch_size)\n",
    "    \n",
    "    train_losses.append(train_loss)\n",
    "    valid_losses.append(valid_loss)\n",
    "    \n",
    "    # Checkpoint\n",
    "    if epoch % checkpoint_freq ==0:\n",
    "        save_model(epoch, lstm, path)\n",
    "        \n",
    "# Last checkpoint\n",
    "save_model(num_epochs, lstm, path)\n",
    "    \n",
    "print(\"\\n\\n\\nOptimization ended.\\n\")\n",
    "\n",
    "plt.plot(train_losses, color=\"darkcyan\", label=\"train\")\n",
    "plt.plot(valid_losses, color=\"tomato\",label=\"validation\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
